/*
  Copyright (C) 2008  Dmitry V. Levin <ldv@altlinux.org>

  "lock" builtin implementation for GNU bash.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/

#include <config.h>

#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/file.h>

#include "builtins.h"
#include "shell.h"
#include "bashgetopt.h"
#include "common.h"
#include "filecntl.h"

static int
lock_file(const char *fname, int operation, int verbose)
{
	int fd, flags = O_RDONLY | O_NOCTTY;

	if (operation & LOCK_NB)
		flags |= O_NONBLOCK;
	fd = open(fname, flags | O_CREAT, 0666);
	if (fd < 0 && errno == EISDIR)
		fd = open(fname, flags);
	if (fd < 0)
	{
		builtin_error("Failed to open file %s: %m", fname);
		return -1;
	}

	fd = move_to_high_fd(fd, 1, -1);
	SET_CLOSE_ON_EXEC(fd);

	if (flock(fd, operation) == 0)
		return 0;

	if (verbose)
		builtin_error("Failed to place a lock on file %s: %m",
			      fname);
	close(fd);
	return -1;
}

static int
lockf_builtin (WORD_LIST *list)
{
	int opt, operation = LOCK_EX, nonblock = 0, verbose = 0;

	reset_internal_getopt();
	while ((opt = internal_getopt(list, "ensvx")) != -1)
	{
		switch (opt)
		{
			case 'e': case 'x':
				operation = LOCK_EX;
				break;
			case 's':
				operation = LOCK_SH;
				break;
			case 'n':
				nonblock = LOCK_NB;
				break;
			case 'v':
				verbose = 1;
				break;
			default:
				builtin_usage();
				return EX_USAGE;
		}
	}
	list = loptend;

	if (!list || list->next)
	{
		builtin_usage();
		return EX_USAGE;
	}

	return lock_file(list->word->word, operation | nonblock, verbose) ?
		EXECUTION_FAILURE : EXECUTION_SUCCESS;
}

static char *lockf_doc[] = {
	"Locks a specified file, which is created if it does not already exist.",
	"File descriptor of the opened file has the close-on-exec flag set.",
	"Options have the following meanings:",
	" ",
	"    -s      obtain a shared lock, sometimes called a read lock",
	"    -e, -x  obtain an exclusive lock, sometimes called a write lock",
	"            (this is the default)",
	"    -n      fail rather than wait if the lock cannot be immediately acquired",
	"    -v      complain when lock attempt fails",
	NULL
};

struct builtin lockf_struct = {
	"lockf",			/* builtin name */
	lockf_builtin,			/* function implementing the builtin */
	BUILTIN_ENABLED,		/* initial flags for builtin */
	lockf_doc,			/* array of long documentation strings. */
	"lockf [-ensx] file",		/* usage synopsis; becomes short_doc */
	0				/* reserved for internal use */
};
